/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.mojo.uml_generator;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;

import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.spring.initializer.ApplicationContextHolder;
import com.genia.toolbox.uml_generator.bean.impl.GenerationConfigurationImpl;
import com.genia.toolbox.uml_generator.manager.UmlGeneratorManager;

/**
 * Maven MOJO to generate java sources and configuration from the UML model.
 * 
 * @phase generate-sources
 * @goal uml2model
 */
public class UmlGeneratorMojo
    extends AbstractMojo
{

  /**
   * the logger to log to.
   */
  private static Log mavenLogger;

  /**
   * the {@link UmlGeneratorManager} to use.
   */
  private transient UmlGeneratorManager umlGeneratorManager;

  /**
   * names of the directories that must not be deleted.
   */
  private static final String[] NOT_TO_DELETE = { ".", "..", ".svn", "CVS" };

  /**
   * The default maven project object.
   * 
   * @parameter expression="${project}"
   * @required
   */
  private MavenProject project;

  /**
   * the {@link File} used as a timestamp for the last generation.
   * 
   * @parameter expression="${basedir}/target/umlGenerator.timestamp"
   * @required
   */
  private transient File timeStampFile;

  /**
   * Wether to clean the generated directories before running the model
   * generation.
   * 
   * @parameter expression="true"
   * @required
   */
  private transient boolean cleanGeneratedDirectories;

  /**
   * the locales to generate beside the default.
   * 
   * @parameter
   * @required
   */
  private transient String[] locales;

  /**
   * Location of the source files.
   * 
   * @parameter expression="${basedir}/src/main/java"
   * @required
   */
  private transient File srcDir;

  /**
   * the name of the package that must contains the configuration files.
   * 
   * @parameter expression="${project.groupId}.${project.artifactId}"
   * @required
   */
  private transient String configurationPackage;

  /**
   * Location of the resource files.
   * 
   * @parameter expression="${basedir}/src/main/resources"
   * @required
   */
  private transient File resourcesDir;

  /**
   * Location of the source files.
   * 
   * @parameter expression="${basedir}/src/main/java-generated"
   * @required
   */
  private transient File generatedSrcDir;

  /**
   * Location of the resource files.
   * 
   * @parameter expression="${basedir}/src/main/resources-generated"
   * @required
   */
  private transient File generatedResourcesDir;

  /**
   * Location of the models.
   * 
   * @parameter expression="${basedir}/src/main/model"
   * @required
   */
  private transient File modelDir;

  /**
   * URL of the Jalopy configuration.
   * 
   * @parameter
   */
  private transient String jalopyUrl;

  /**
   * whether to add the generated resources to the sources.
   * 
   * @parameter expression="false"
   */
  private transient boolean addSourcesToResources;



  /**
   * the main method of the plugin.
   * 
   * @throws MojoExecutionException
   *           if an error occured
   * @see org.apache.maven.plugin.Mojo#execute()
   */
  public void execute()
      throws MojoExecutionException
  {
    try {
      setMavenLogger(getLog());
      if (!modelDir.exists()) {
        return;
      }

      /*
       * Adding the needed directories to the build paths.
       */
      project.addCompileSourceRoot(generatedSrcDir.getAbsolutePath());

      Resource resource = new Resource();
      resource.setDirectory(generatedResourcesDir.getAbsolutePath());
      project.addResource(resource);
      if (addSourcesToResources) {
        resource = new Resource();
        resource.setDirectory(generatedSrcDir.getAbsolutePath());
        project.addResource(resource);
      }

      /*
       * if the compilation is up to date, do not do anything more. 
       */
      if (!mustCompileModel(modelDir)) {
        getLog().info("The model seems to be up to date, not generating.");
        return;
      }

      umlGeneratorManager = (UmlGeneratorManager) ApplicationContextHolder.getApplicationContext().getBean("umlGeneratorManager");
      cleanDirectories();
      GenerationConfigurationImpl configuration = new GenerationConfigurationImpl();
      configuration.setGeneratedResourcesDir(generatedResourcesDir);
      configuration.setGeneratedSrcDir(generatedSrcDir);
      configuration.setResourcesDir(resourcesDir);
      configuration.setSrcDir(srcDir);
      configuration.setLocales(locales);
      configuration.setConfigurationPackage(rectifyPackageName(configurationPackage));
      if (jalopyUrl != null) {
        configuration.setJalopyConfiguration(new URL(jalopyUrl));
      }
      Collection<File> xmiFiles = getModels(modelDir);
      configuration.setXmiFiles(xmiFiles.toArray(new File[xmiFiles.size()]));
      umlGeneratorManager.generateModele(configuration);
      timeStampFile.getParentFile().mkdirs();
      timeStampFile.createNewFile();
    }
    catch (Exception e) {
      throw new MojoExecutionException("Error creating model", e);
    }
  }



  /**
   * returns wether the compilation of the model must be done. This won't be the
   * case if the last compilation is newer than the newest modified model file.
   * 
   * @param root
   *          the directory containing the model files.
   * @return <code>true</code> if and only if the compilation must be redone
   */
  private boolean mustCompileModel(File root)
  {
    if (!timeStampFile.exists()) {
      return true;
    }
    long timestamp = timeStampFile.lastModified();
    for (File file : getFilesFromDirectory(root)) {
      if (file.lastModified() > timestamp) {
        return true;
      }
    }
    return false;
  }



  /**
   * rectify the package name to conform to java standard.
   * 
   * @param packageName
   *          the old package name
   * @return the packageName once rectified
   */
  static String rectifyPackageName(final String packageName)
  {
    return packageName.toLowerCase().replace('-', '_');
  }



  /**
   * returns the {@link Collection} of {@link File} that contains model in the
   * root directory.
   * 
   * @param root
   *          the directory to search from
   * @return the {@link Collection} of {@link File} that contains model in the
   *         root directory
   * @throws BundledException
   *           if an error occurred
   */
  private Collection<File> getModels(File root)
      throws BundledException
  {
    List<File> res = new ArrayList<File>();
    for (File file : getFilesFromDirectory(root)) {
      final File xmiFromFile = getXmiFromFile(file);
      if (xmiFromFile != null) {
        res.add(xmiFromFile);
      }
    }
    return res;
  }



  /**
   * returns all the {@link File} contained in the root directory given in
   * parameter.
   * 
   * @param root
   *          the directory to search from
   * @return all the {@link File} contained in the root directory given in
   *         parameter
   */
  private Collection<File> getFilesFromDirectory(File root)
  {
    List<File> res = new ArrayList<File>();
    for (File file : root.listFiles()) {
      if (file.getName().startsWith(".")) {
        continue;
      }
      if (file.isDirectory()) {
        res.addAll(getFilesFromDirectory(file));
      }
      else {
        res.add(file);
      }
    }
    return res;
  }



  /**
   * Retrieve an xmi file from a file.
   * 
   * @param file
   *          the file to consider
   * @return an xmi file contain in the file or the file itself if it is already
   *         a xmi file
   * @throws BundledException
   *           if an error occurred
   */
  private File getXmiFromFile(File file)
      throws BundledException
  {
    if (file.getName().endsWith(".xmi")) {
      return file;
    }
    if (file.getName().endsWith(".zargo")) {
      return umlGeneratorManager.getXmiFileFromArgoFile(file);
    }
    if (file.getName().endsWith(".zargo~")) {
      return null;
    }
    else {
      throw new TechnicalIOException(new IOException("Unable to get a xmi file from " + file.getAbsolutePath()));
    }
  }



  /**
   * clean the directories that contain only generated files.
   */
  private void cleanDirectories()
  {
    if (cleanGeneratedDirectories) {
      deleteSubDirectories(generatedResourcesDir);
      deleteSubDirectories(generatedSrcDir);
    }
  }



  /**
   * delete a directory recursively.
   * 
   * @param root
   *          the directory to delete
   */
  private void deleteSubDirectories(File root)
  {
    if (root.isDirectory()) {
      for (File file : root.listFiles()) {
        boolean toDelete = true;
        for (String notToDelete : NOT_TO_DELETE) {
          if (file.getName().equals(notToDelete)) {
            toDelete = false;
          }
        }
        if (toDelete) {
          if (file.isDirectory()) {
            deleteSubDirectories(file);
          }
          file.delete();
        }
      }
    }
  }



  /**
   * getter for the mavenLogger property.
   * 
   * @return the mavenLogger
   */
  public static Log getMavenLogger()
  {
    return mavenLogger;
  }



  /**
   * setter for the mavenLogger property.
   * 
   * @param mavenLogger
   *          the mavenLogger to set
   */
  public static void setMavenLogger(Log mavenLogger)
  {
    UmlGeneratorMojo.mavenLogger = mavenLogger;
  }
}
